#ifndef D_FRUSTUM_H
#define D_FRUSTUM_H

#include <math.h>
#include <helpers\math\plane.h>
#include <helpers\math\matrix.h>

namespace DelGE
{
	namespace Helpers
	{
		namespace Math
		{
			enum DelGE_FrustumPlane
			{
				FrustumPlane_Right,
				FrustumPlane_Left,
				FrustumPlane_Bottom,
				FrustumPlane_Top,
				FrustumPlane_Far,
				FrustumPlane_Near,
				FrustumPlane_Count
			};

			struct Frustum
			{
				Plane planes[6];

				Frustum();
				~Frustum();

				static Frustum CreateFromProjectionMatix(const Matrix& rProjectionMatrix);

				void SetPlane(DelGE_FrustumPlane ePlane, const Vector3& rNormal, float fDistanceFromOrigin);
				void Normailize();

				int TestShpere(const Vector3 &rCenter, float fRadius) const;
				float DistanceFromNearPlane(const Vector3 &rCenter) const;
			};

			inline Frustum Frustum::CreateFromProjectionMatix(const Matrix& rProjectionMatrix)
			{
				Frustum frustum;

				frustum.SetPlane(FrustumPlane_Left,		Vector3(rProjectionMatrix.fData[3] + rProjectionMatrix.fData[0],
														rProjectionMatrix.fData[7] + rProjectionMatrix.fData[4],
														rProjectionMatrix.fData[11] + rProjectionMatrix.fData[8]),
														rProjectionMatrix.fData[15] + rProjectionMatrix.fData[12]);

				frustum.SetPlane(FrustumPlane_Right,	Vector3(rProjectionMatrix.fData[3] - rProjectionMatrix.fData[0],
														rProjectionMatrix.fData[7] - rProjectionMatrix.fData[4],
														rProjectionMatrix.fData[11] - rProjectionMatrix.fData[8]),
														rProjectionMatrix.fData[15] - rProjectionMatrix.fData[12]);

				frustum.SetPlane(FrustumPlane_Top,		Vector3(rProjectionMatrix.fData[3] + rProjectionMatrix.fData[1],
														rProjectionMatrix.fData[7] + rProjectionMatrix.fData[5],
														rProjectionMatrix.fData[11] + rProjectionMatrix.fData[9]),
														rProjectionMatrix.fData[15] + rProjectionMatrix.fData[13]);

				frustum.SetPlane(FrustumPlane_Bottom,	Vector3(rProjectionMatrix.fData[3] - rProjectionMatrix.fData[1],
														rProjectionMatrix.fData[7] - rProjectionMatrix.fData[5],
														rProjectionMatrix.fData[11] - rProjectionMatrix.fData[9]),
														rProjectionMatrix.fData[15] - rProjectionMatrix.fData[13]);

				frustum.SetPlane(FrustumPlane_Near,		Vector3(rProjectionMatrix.fData[3] + rProjectionMatrix.fData[2],
														rProjectionMatrix.fData[7] + rProjectionMatrix.fData[6],
														rProjectionMatrix.fData[11] + rProjectionMatrix.fData[10]),
														rProjectionMatrix.fData[15] + rProjectionMatrix.fData[14]);

				frustum.SetPlane(FrustumPlane_Far,		Vector3(rProjectionMatrix.fData[3] - rProjectionMatrix.fData[2],
														rProjectionMatrix.fData[7] - rProjectionMatrix.fData[6],
														rProjectionMatrix.fData[11] - rProjectionMatrix.fData[10]),
														rProjectionMatrix.fData[15] - rProjectionMatrix.fData[14]);

				frustum.Normailize();

				return frustum;
			}

			inline void Frustum::Normailize()
			{
				for (int iPlane = 0; iPlane < FrustumPlane_Count; iPlane ++)
				{
					this->planes[iPlane].Normailize();
				}
			}

			inline int Frustum::TestShpere(const Vector3 &rCenter, float fRadius) const
			{
				int iPlane;

				for (iPlane = 0; iPlane < FrustumPlane_Count; iPlane ++)
				{

					if ( this->planes[iPlane].GetNormal()[0] * rCenter.x + 
						 this->planes[iPlane].GetNormal()[1] * rCenter.y + 
						 this->planes[iPlane].GetNormal()[2] * rCenter.z + 
						 this->planes[iPlane].GetDistanceFromOrigin() <= -fRadius)
						 return 0;
				}

				return 1;
			}

			inline float Frustum::DistanceFromNearPlane(const Vector3 &rCenter) const
			{
				return this->planes[FrustumPlane_Near].GetNormal()[0] * rCenter.x + 
						 this->planes[FrustumPlane_Near].GetNormal()[1] * rCenter.y + 
						 this->planes[FrustumPlane_Near].GetNormal()[2] * rCenter.z + 
						 this->planes[FrustumPlane_Near].GetDistanceFromOrigin();
			}
		}
	}
}

#endif